#pragma once

// #include "ui.hpp"
#include "app.hpp"
#include "between_face_common.hpp"
#include "oled.hpp"

enum class menu_type : uint8_t {
    no_value = 0,  // no value 类型的菜单项作用类似按钮，代表要立即执行的操作。
                   // 如果要用菜单项显示注释或纯文本，可将类型设为comment。

    unsigned_value,
    signed_value,
    hex_value,
    float_value,

    option,
    comment,  // 注释行，与option 类似，可以用字符串数组显示帮助信息，
              // 但值是可选的，如果len == 0，表示没有额外的帮助信息。
    check,

    text,
};


// constexpr size_t MENU_ITEM_STR_LEN = 4;  // 菜单项字符串最大长度

// static_assert(MENU_ITEM_STR_LEN >= 4);


using ConstantStrArrayPtr = const char *const *;
using ConstantStrArray = const char *const[];


template <size_t N, typename T>
constexpr size_t len_of_array(T (&arr)[N]) {
    return N;
}


union MenuItemData {
    uint32_t u;

    int32_t s;

    float f;  // 如果是浮点数类型，表示浮点数的值

    char *text;  // 如果是字符串类型，指向字符串缓冲区，字符串以\0 结尾，
                 // 编辑字符串时，有可能不会从头开始，而是在中间随机的插入字符，
                 // 所以处理字符串时，需要ltrim，去掉左侧的空白和无效字符。
                 // 同样，index 里存储的字符串长度也可能是不可靠的。

    ConstantStrArrayPtr list;  // 如果是选项类型，表示选项列表
};


struct MenuItem {
    const char *label;  // 菜单项名称
    menu_type type;     // 菜单项类型

    uint8_t id;  // 菜单项ID，用于标识菜单项，或存储与菜单项绑定的其他信息
    // bool clickable;  // 是否可点击

    uint8_t len;  // 如果是整数类型，表示整数的位数，主要用于指导编辑数据时需要多少个SpinBox；
                  // 如果是浮点，表示整数部分的位数，
                  // 如果是选项类型，表示选项的数量
                  // 如果是文本类型，表示字符串缓冲区总长度，包括\0

    uint8_t index;  // 如果是选项类型，表示当前选中的选项索引
                    // 如果是check 类型，表示bool 值，0 false，1 true
                    // 如果是浮点类型，表示精度
                    // 如果是整数类型，表示乘10^index
                    // 如果是hex 类型，表示乘16^index
                    // 如果是文本类型，表示输入的字符串长度，包括\0

    // MenuItem 只关心菜单项的数据存储和显示，
    // 输入验证可以放在输入界面中处理，比如在输入超限后就不触发spin box 的dec 和inc，
    // 或者另外增加个验证器对象，和MenuItem 一对一绑定。
    // 另一方面，如果在MenuItem 里增加min 和max 成员作为验证，
    // 会增加结构复杂度，占用更多空间，很多时候只靠min 和max 又不足以验证。
    MenuItemData data;

   private:
    uint32_t dec_multiplier() const {
        switch (index) {
            case 0:
                return 1;

            case 1:
                return 10;

            case 2:
                return 100;

            case 3:
                return 1000;

            case 4:
                return 10000;

            default:
                uint32_t v = 1;
                for (uint8_t i = index; i > 0; --i) {
                    v *= 10;
                }
                return v;
        }
    }


    uint32_t hex_multiplier() const {
        switch (index) {
            case 0:
                return 1;

            case 1:
                return 0x10;

            case 2:
                return 0x100;

            case 3:
                return 0x1000;

            case 4:
                return 0x10000;

            default:
                uint32_t v = 1;
                for (uint8_t i = index; i > 0; --i) {
                    v <<= 4;
                }
                return v;
        }
    }


   public:
    MenuItem &item_label(const char *label) {
        this->label = label;
        return *this;
    }


    MenuItem &item_id(uint8_t i) {
        this->id = i;
        return *this;
    }


    bool checked() const {
        return type == menu_type::check && index != 0;
    }


    uint8_t number_len() const {
        switch (type) {
            case menu_type::unsigned_value:
            case menu_type::signed_value:
            case menu_type::hex_value:
            case menu_type::float_value:
                return len;

            default:
                return 0;
        }
    }


    uint8_t multiplier() const {
        switch (type) {
            case menu_type::unsigned_value:
            case menu_type::signed_value:
                return dec_multiplier();

            case menu_type::hex_value:
                return hex_multiplier();

            default:
                return 1;
        }
    }


    uint8_t float_precision() const {
        return type == menu_type::float_value ? index : 0;
    }


    uint8_t option_count() const {
        return (is_option() || is_comment()) ? len : 0;
    }


    uint8_t option_index() const {
        return type == menu_type::option ? index : 0;
    }


    const char *option_text() const {
        if (type == menu_type::option && index < len) {
            return data.list[index];
        }
        return nullptr;  // 如果索引越界，返回nullptr
    }


    void as_no_value() {
        type = menu_type::no_value;
        // len = 0;
        // index = 0;
    }


    void as_comment() {
        type = menu_type::comment;
        len = 0;  // 注释行没有长度
        // index = 0;  // 注释行没有索引
    }


    void as_help(ConstantStrArrayPtr text_list, uint8_t count) {
        type = menu_type::comment;
        data.list = text_list;
        len = count;
        index = 0;
    }


    template <uint8_t N>
    void as_help(const char *const (&text_list)[N]) {
        as_help(text_list, N);
    }


    void as_float(float value, uint8_t num_len = 3, uint8_t precision = 2) {
        type = menu_type::float_value;
        data.f = value;
        index = precision;
        len = num_len;
    }


    void as_unsigned(uint32_t value, uint8_t num_len = 3, uint8_t index = 0) {
        type = menu_type::unsigned_value;
        data.u = value;
        len = num_len;
        this->index = index;
    }


    void as_signed(int32_t value, uint8_t num_len = 3, uint8_t index = 0) {
        type = menu_type::signed_value;
        data.s = value;
        len = num_len;
        this->index = index;
    }


    void as_hex(uint32_t value, uint8_t num_len = 3, uint8_t index = 0) {
        type = menu_type::hex_value;
        data.u = value;
        len = num_len;
        this->index = index;
    }


    void as_option(ConstantStrArrayPtr options, uint8_t count, uint8_t selected_index = 0) {
        type = menu_type::option;
        data.list = options;
        len = count;
        index = selected_index < count ? selected_index : 0;  // 确保索引不越界
    }


    template <uint8_t N>
    void as_option(const char *const (&options)[N], uint8_t selected_index = 0) {
        as_option(options, N, index);
    }


    void set_option_index(uint8_t selected_index) {
        if (type == menu_type::option && selected_index < len) {
            index = selected_index;  // 确保索引不越界
        }
    }


    void as_check(bool checked) {
        type = menu_type::check;
        index = checked ? 1 : 0;  // 使用0或1表示false或true
    }


    void as_text(char *buf, uint8_t len) {
        if (buf == nullptr || len < 2) {
            return;
        }

        type = menu_type::text;
        len = len;
        index = 0;
    }


    template <uint8_t N>
    void as_text(char (&buf)[N]) {
        as_text(buf, N);
    }


    const char *text() const {
        if (type == menu_type::text) {
            return data.text;
        }
        return nullptr;  // 如果不是文本类型，返回nullptr
    }


    // 返回指向文本缓冲区的指针，允许修改文本内容
    char *text_buffer() {
        return const_cast<char *>(text());
    }


    uint8_t text_len() const {
        return type == menu_type::text ? index : 0;  // 如果是文本类型，返回长度，否则返回0
    }


    // 返回最大文本长度，包括1 字节\0
    uint8_t max_text_len() const {
        return len;
    }


    bool is_integer() const {
        return type == menu_type::unsigned_value || type == menu_type::signed_value || type == menu_type::hex_value;
    }


    bool is_unsigned() const {
        return type == menu_type::unsigned_value || type == menu_type::hex_value;
    }


    bool is_hex() const {
        return type == menu_type::hex_value;
    }


    bool is_signed() const {
        return type == menu_type::signed_value;
    }


    bool is_float() const {
        return type == menu_type::float_value;
    }


    bool is_option() const {
        return type == menu_type::option;
    }


    bool is_check() const {
        return type == menu_type::check;
    }


    bool is_text() const {
        return type == menu_type::text;
    }


    bool is_comment() const {
        return type == menu_type::comment;
    }


    bool is_empty_comment() const {
        return is_comment() && len == 0;
    }


    bool is_help() const {
        return type == menu_type::comment && len > 0;
    }


    bool is_no_value() const {
        return type == menu_type::no_value;
    }


    uint32_t unsigned_value() const {
        return data.u * multiplier();
    }


    int32_t signed_value() const {
        return data.s * multiplier();
    }


    float float_value() const {
        return data.f;
    }
};


class MenuPage {
   private:
    beef::InterfaceBase *_parent;  // TODO

    const char *_clickable_cursor = nullptr;      // 用来表示光标的字符
    const char *_not_clickable_cursor = nullptr;  // 用来表示光标的字符
    const char *_comment_cursor = nullptr;        // 用来表示注释行的字符
    const char *_delimiter = nullptr;             // 用来分隔菜单项label 和value 的字符

    MenuItem *_items;  // 菜单项数组

    uint32_t _not_clickable_mask = 0;  // 不可点击的菜单项掩码

    uint8_t _item_count;          // 菜单项数量
    uint8_t _selected_index = 0;  // 当前选中的菜单项索引

    bool _changed = false;  // 菜单项是否发生变化

    uint8_t _display_row_count = 0;  // 屏幕上可见的菜单项行数
    uint8_t _mid_row = 0;            // 中间行索引，屏幕行数 / 2
    uint8_t _display_offset_cursor_x = 0;
    uint8_t _display_offset_cursor_y = 0;
    uint8_t _row_hight = 0;  // 行高，单位像素
    uint8_t _width = 0;


    uint8_t visible_start_index() const {
        // 计算可见菜单项的起始索引
        // 算法原理参考：https://blog.csdn.net/Etberzin/article/details/149390359
        if (_item_count <= _display_row_count) {
            return 0;  // 所有菜单项都可见
        }
        else if (_selected_index < _mid_row) {
            return 0;  // 光标在顶部边界
        }
        else if (_selected_index >= _item_count - _mid_row) {
            return _item_count - _display_row_count;  // 光标在底部边界
        }
        else {
            return _selected_index - _mid_row;  // 光标在中间部分
        }
    }


    bool not_drawable() const {
        // 如果没有可见行或行高为0，则无法绘制菜单项
        return _display_row_count == 0 || _row_hight == 0 || _width == 0;
    }


    // 清除屏幕上显示菜单项的区域
    void clear_area() {
        if (not_drawable()) {
            return;
        }

        auto start_y = _display_offset_cursor_y;
        if (start_y > _row_hight) {
            start_y -= _row_hight - 1;
        }

        clear_box(_display_offset_cursor_x, start_y, _width, _row_hight * _display_row_count);
    }


   public:
    static constexpr size_t CLICKABLE_FLAG_MAX_INDEX = (sizeof(_not_clickable_mask) * 8) - 1;

    MenuPage(MenuItem *items, uint8_t item_count, uint8_t row_count, beef::InterfaceBase *parent = nullptr) :
        _parent(parent),
        _items(items),
        _item_count(item_count),
        _display_row_count(row_count),
        _mid_row(row_count / 2) {
        // 限制菜单项数量最大为32
        // if (item_count > 32) {
        //     _item_count = 32;
        // }
    }


    void set_placing(uint8_t row_height, uint8_t width, uint8_t offset_x, uint8_t offset_y) {
        _row_hight = row_height;
        _width = width;
        _display_offset_cursor_x = offset_x;
        _display_offset_cursor_y = offset_y;
    }


    uint8_t row_height() const {
        return _row_hight;
    }


    void scroll_down(bool cycle = false) {
        if (_item_count == 0) return;

        if (_selected_index < _item_count - 1) {
            ++_selected_index;
            _changed = true;
        }
        else if (cycle) {
            _selected_index = 0;
            _changed = true;
        }
    }


    void scroll_up(bool cycle = false) {
        if (_item_count == 0) return;

        if (_selected_index > 0) {
            --_selected_index;
            _changed = true;
        }
        else if (cycle) {
            _selected_index = _item_count - 1;
            _changed = true;
        }
    }


    void scroll_to(uint8_t index) {
        if (index < _item_count) {
            _selected_index = index;
            _changed = true;
        }
    }


    void scroll_to_top() {
        scroll_to(0);
    }


    void scroll_to_bottom() {
        if (_item_count > 0) {
            scroll_to(_item_count - 1);
        }
    }


    MenuItem &selected_item() {
        return _items[_selected_index];
    }


    const MenuItem &selected_item() const {
        return _items[_selected_index];
    }


    uint8_t selected_index() const {
        return _selected_index;
    }


    bool is_top() const {
        return _selected_index == 0;
    }


    bool is_bottom() const {
        return _selected_index == (_item_count - 1);
    }


    MenuItem *item(uint8_t index) {
        if (index < _item_count) {
            return &_items[index];
        }
        return nullptr;
    }


    size_t item_count() const {
        return _item_count;
    }


    bool changed() const {
        return _changed;
    }


    void set_changed(bool changed) {
        _changed = changed;
    }


    bool clickable(uint8_t index) const {
        if (index >= _item_count || index > CLICKABLE_FLAG_MAX_INDEX)
            return false;
        return (_not_clickable_mask & (1 << index)) == 0;
    }


    /**
     * @brief 设置菜单项是否可点击
     *
     * 只有前32 个菜单项可以设置可点击状态。
     *
     * @param index 菜单项索引
     * @param clickable 是否可点击
     */
    void set_clickable(uint8_t index, bool clickable) {
        if (index < _item_count && index <= CLICKABLE_FLAG_MAX_INDEX) {
            if (clickable) {
                _not_clickable_mask &= ~(1 << index);
            }
            else {
                _not_clickable_mask |= (1 << index);
            }
        }
    }


    /**
     * @brief 根据当前选中的行，在屏幕选定区域内绘制可见的菜单项
     *
     * 为了简化绘制逻辑，不管菜单操作正在向上或向下滚动，只要选中同一个菜单项，绘制的内容就是相同的。
     *
     * 一般的滚动菜单绘制时需要考虑滚动方向，比如，菜单项总共有四行，而屏幕上总共能显示两行，那么向下滚动时，
     * 光标会固定在第二行，而向上滚动时，光标会固定在第一行。
     *
     * 向下滚动时：
     *
     *    菜单项1
     * >  菜单项2
     * >  菜单项3   👈  ↓
     *    菜单项4
     *
     *    菜单项1
     *    菜单项2
     * >  菜单项3
     * >  菜单项4   👈  ↓
     *
     * 向上滚动时：
     *
     *    菜单项1
     *    菜单项2
     * >  菜单项3
     * >  菜单项4   👈  ↑
     *
     *    菜单项1
     *    菜单项2
     * >  菜单项3   👈  ↑
     * >  菜单项4
     *
     * 左侧两个尖括号表示屏幕上可见的两行。选中菜单项3 时，如果是向下滚动，光标会在第二行，而向上滚动时，光标会在第一行。
     * 而且，向下时，选中菜单项3，可见的菜单项是菜单项2 和菜单项3，而向上滚动时，可见的菜单项是菜单项3 和菜单项4。
     * 如果屏幕上显示的行数更多，这套逻辑还会更麻烦。
     *
     * 所以这里采用简化的方案，如果屏幕上能显示三行，则光标会暂时固定在中间第二行，这样如果选中了菜单项2，那么屏幕上要显示的
     * 一定是1 到3 三个菜单项，如果选中了菜单项3，那么屏幕上要显示的就是2 到4 三个菜单项。
     *
     * 当菜单滚动到开头或结尾时，才移动光标。效果如下：
     *
     * 向下滚动时：
     *
     * >  菜单项1    👈  ↓
     * >  菜单项2
     * >  菜单项3
     *    菜单项4
     *
     * >  菜单项1
     * >  菜单项2    👈  ↓
     * >  菜单项3
     *    菜单项4
     *
     *    菜单项1
     * >  菜单项2
     * >  菜单项3    👈  ↓
     * >  菜单项4
     *
     *    菜单项1
     * >  菜单项2
     * >  菜单项3
     * >  菜单项4    👈  ↓
     *
     * 光标位置算法：
     *
     * 设屏幕行数为3，3 / 2 = 1，则所有菜单项中，正数第一个项的光标在第一行，倒数第一个的光标在倒数第一行，
     * 选中其他菜单项时，光标都在中间的第二行；
     * 如果屏幕行数为5，5 / 2 = 2，则正数第一个项的光标在第一行，第二个的光标在第二行，倒数第一个项光标在倒数第一行，
     * 倒数第二个项的光标在倒数第二行，选中其他菜单项时，光标都在中间的第三行。
     *
     * 依次类推。如果屏幕行数为偶数，比如行数为2，则规则和行数为3 时相同。
     *
     * 如果菜单项数小于等于屏幕行数，则光标行数和菜单项数相同，不做以上计算。如果屏幕只有一行，则光标始终在第一行，
     * 也不用计算。
     *
     * 可见菜单项算法：
     *
     * 先计算光标位置，然后根据光标位置和屏幕行数，计算可见菜单项的起始索引和结束索引。
     * 比如，如果光标在第一行，屏幕行数为3，则可见菜单项的起始索引为0，结束索引为2；
     * 如果光标在第二行，屏幕行数为3，则可见菜单项的起始索引是当前选中索引减1；
     *
     *
     * @param is_initial
     * @return true
     * @return false
     */
    bool draw(bool is_initial) {
        if (not_drawable()) {
            // 如果没有可见行或行高为0，则无法绘制菜单项
            return false;
        }

        if (!is_initial) {
            if (!_changed) {
                // 没有变化，不需要重绘
                return false;
            }
        }

        _changed = false;
        clear_area();
        u8g2.setFont(MENU_FONT);
        // uint8_t cursor_row = selected_row();
        uint8_t visible_start = visible_start_index();
        uint8_t count = min(_display_row_count, _item_count);

        for (uint8_t i = 0; i < count; ++i) {
            uint8_t index = visible_start + i;
            // if (index >= _item_count) break;  // 防止越界

            MenuItem &item = _items[index];
            u8g2.setCursor(_display_offset_cursor_x, _display_offset_cursor_y + i * _row_hight);

            if (index == _selected_index) {
                // 绘制选中项的光标
                if (item.is_comment()) {
                    u8g2.print(_comment_cursor ? _comment_cursor : "! ");  // 注释行
                }
                else {
                    if (clickable(index)) {
                        u8g2.print(_clickable_cursor ? _clickable_cursor : "> ");  // 可点击项，前面加上">"符号
                    }
                    else {
                        u8g2.print(_not_clickable_cursor ? _not_clickable_cursor : "# ");  // 不可点击项，前面加上"#"符号
                    }
                }
            }

            u8g2.print(item.label);
            if (!(item.is_comment() || item.is_no_value())) {
                // 如果菜单项有值，则打印分隔符
                u8g2.print(_delimiter ? _delimiter : " ");
            }

            switch (item.type) {
                case menu_type::no_value:
                case menu_type::comment:  // comment 类型可能含有附加帮助信息，但不直接显示
                    break;

                case menu_type::unsigned_value:
                    u8g2.print(item.unsigned_value());
                    break;

                case menu_type::signed_value:
                    u8g2.print(item.signed_value());
                    break;

                case menu_type::hex_value:
                    u8g2.print("H");
                    u8g2.print(item.unsigned_value(), HEX);
                    break;

                case menu_type::float_value:
                    u8g2.print(item.data.f, item.float_precision());
                    break;

                case menu_type::option: {
                    if (item.index >= item.len) {
                        item.index = 0;  // 确保索引不越界
                    }
                    const char *option = item.data.list[item.index];
                    if (option != nullptr) {
                        u8g2.print(option);
                    }
                } break;

                case menu_type::check:
                    if (item.checked()) {
                        u8g2.print("ON");
                    }
                    else {
                        u8g2.print("OFF");
                    }
                    break;

                case menu_type::text: {
                    // 需要ltrim
                    uint8_t i = 0;
                    for (; i < item.max_text_len() - 1; ++i) {
                        if (item.data.text[i] != '\0' && item.data.text[i] != ' ')
                            break;
                    }
                    u8g2.print(reinterpret_cast<const char *>(&item.data.text[i]));
                } break;
            }
        }

        return true;  // 返回true表示菜单项已成功绘制
    }
};


/**
 * @brief SpinBox 类可用于选择数字或选项列表中的值，相比MenuPage 简化了显示功能，多个Spinbox 组合使用时优化资源占用。
 *
 */
class SpinBox {
   private:
    ConstantStrArrayPtr _list = nullptr;  // 选项列表

    SpinBox *_prev = nullptr;  // 上一个SpinBox，用于自动处理数字进位

    uint8_t _min = 0;
    uint8_t _max = 0;      // 当_list != nullptr 时，表示list 长度
    uint8_t _current = 0;  // 当_list != nullptr 时，表示当前选中的索引

    uint8_t _flag = 0;  // 标志位

    static constexpr uint8_t FLAG_INDEX_CHANGED = 0;    // 标志位，表示值已改变
    static constexpr uint8_t FLAG_INDEX_FOCUSED = 1;    // 标志位，表示SpinBox 已经获得焦点
    static constexpr uint8_t FLAG_INDEX_HEX = 2;        // 标志位，表示SpinBox 按16 进制显示数字
    static constexpr uint8_t FLAG_INDEX_NOT_CYCLE = 3;  // 标志位，设为1 使inc 和dec 到达max 或min 时不自动循环

    uint8_t _x;
    uint8_t _y;  // SpinBox 在屏幕上的位置
    uint8_t _w;  // SpinBox 的宽度
    uint8_t _h;  // SpinBox 的高度


   protected:
    // 与this 链接的另一个box 发生上溢
    void linked_box_overflow() {
        inc();
    }

    // 与this 链接的另一个box 发生下溢
    void linked_box_underflow() {
        dec();
    }


   public:
    SpinBox() = default;


    SpinBox(ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) :
        _list(list), _min(0), _max(count), _current(current) {
        // 确保当前索引不越界
        if (_current >= _max) {
            _current = 0;
        }
    }


    /**
     * @brief 构造函数，创建一个SpinBox，指定最小值、最大值和当前值。
     *
     * @param min 最小值
     * @param max 最大值，取值范围：[min, max)
     * @param current 当前值，默认为0
     */
    SpinBox(uint8_t min, uint8_t max, uint8_t current = 0) :
        _list(nullptr), _min(min), _max(max), _current(current) {
        // 确保当前值在范围内
        if (_current < _min || _current > _max) {
            _current = min;
        }
    }


    /**
     * @brief 按从右到左的方向依次链接表示各位数字的SpinBox
     *
     * @param prev 指向表示左侧数字的SpinBox
     */
    void link(SpinBox *prev) {
        this->_prev = prev;
    }


    void set_list(ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) {
        _list = list;
        _max = count;
        _min = 0;                                  // 当有选项列表时，最小值为0
        _current = current < count ? current : 0;  // 确保当前索引不越界
    }


    /**
     * @brief 设置取值范围：[min, max)
     *
     * @param min
     * @param max
     * @param current
     */
    void set_range(uint8_t min, uint8_t max, uint8_t current = 0) {
        _min = min;
        _max = max;
        _current = (current < min || current >= max) ? min : current;  // 确保当前值在范围内
    }


    /**
     * @brief 设置坐标和尺寸
     *
     * x,y 表示SpinBox 中的文本的起始坐标，按照U8g2 的坐标系，起始坐标是文本的基线，
     * 一般在文本的左下角。
     *
     * w,h 表示SpinBox 的宽度和高度，单位是像素。h 包括了文本和光标的高度，计算起点在SpinBox 的左上角。
     * w 是整体宽度，更新文本时，会自动清空屏幕上对应w,h 尺寸的一个矩形区域。
     *
     * 当h 较大时，底部光标和文本的垂直距离较远，反之则较近。h 过小时，光标会覆盖文本。
     * 光标高度2 像素，宽度等于w。
     *
     * @param x
     * @param y
     * @param w
     * @param h
     */
    void set_placing(uint8_t x, uint8_t y, uint8_t w, uint8_t h) {
        _x = x;
        _y = y;
        _w = w;
        _h = h;
    }


    uint8_t current() const {
        return _current;
    }


    const char *current_text() const {
        if (_list != nullptr && _current < _max) {
            return _list[_current];
        }
        return nullptr;  // 如果没有选项列表或索引越界，返回nullptr
    }


    const SpinBox *linked_box() const {
        return _prev;
    }


    SpinBox *linked_box() {
        return _prev;
    }


    bool is_option_list() const {
        return _list != nullptr;
    }


    bool is_range() const {
        return _list == nullptr;
    }


    void set_current(uint8_t value) {
        // 如果没有选项列表，则设置当前值
        if (value >= _min && value <= _max) {
            _current = value;
        }
    }


    bool changed() const {
        return (_flag & (1 << FLAG_INDEX_CHANGED)) != 0;
    }


    void set_changed(bool changed) {
        if (changed) {
            _flag |= (1 << FLAG_INDEX_CHANGED);
        }
        else {
            _flag &= ~(1 << FLAG_INDEX_CHANGED);
        }
    }


    bool focused() const {
        return (_flag & (1 << FLAG_INDEX_FOCUSED)) != 0;
    }


    void set_focused(bool focused) {
        if (focused) {
            _flag |= (1 << FLAG_INDEX_FOCUSED);
        }
        else {
            _flag &= ~(1 << FLAG_INDEX_FOCUSED);
        }
        set_changed(true);  // 设置为已改变状态
    }


    bool cycle() const {
        return (_flag & (1 << FLAG_INDEX_NOT_CYCLE)) == 0;
    }


    void set_cycle(bool cycle) {
        if (!cycle) {
            _flag |= (1 << FLAG_INDEX_NOT_CYCLE);
        }
        else {
            _flag &= ~(1 << FLAG_INDEX_NOT_CYCLE);
        }
    }


    bool is_hex() const {
        return (_flag & (1 << FLAG_INDEX_HEX)) != 0;
    }


    void set_hex(bool hex) {
        if (hex) {
            _flag |= (1 << FLAG_INDEX_HEX);
        }
        else {
            _flag &= ~(1 << FLAG_INDEX_HEX);
        }
        set_changed(true);  // 设置为已改变状态
    }


    uint8_t inc() {
        if (_current < _max - 1) {
            ++_current;
        }
        else if (cycle()) {
            _current = _min;  // 循环到第一个选项

            if (_prev != nullptr) {
                _prev->linked_box_overflow();
            }
        }
        else {
            return _current;
        }

        set_changed(true);
        return _current;
    }


    uint8_t dec() {
        if (_current > _min) {
            --_current;
        }
        else if (cycle()) {
            _current = _max - 1;  // 循环到最后一个选项

            if (_prev != nullptr) {
                // TODO: 这个地方inc 和dec 函数都会递归执行，如果spin box 一串数量比较多，
                // 可能会有问题。
                _prev->linked_box_underflow();
            }
        }
        else {
            return _current;
        }

        set_changed(true);
        return _current;
    }


    /**
     * @brief 汇总当前box 和所有prev box 的值，返回整体数值。
     *
     * 如果用一个spin box 表示一个数字的某一位，比如个位、十位、百位等，
     * 可以用这个函数方便获取整体数值。
     *
     * 应该调用最右侧的SpinBox，才能获取整行SpinBox 代表的数值。
     *
     * 如果spin box 有选项列表，同样按照选中的索引计算总和。这样可以用来一次获取一组选项的值。
     *
     * @param base 基数，默认为10，如果为0，则使用prev box 的最大值作为基数
     * @return uint32_t
     */
    uint32_t sum(uint8_t base = 10) const {
        uint32_t sum = current();
        uint32_t multiplier = 1;

        const SpinBox *prev = this->_prev;
        while (prev != nullptr /*&& prev->is_range()*/) {
            auto b = (base > 0) ? base : prev->_max;  // 如果没有基数，则使用最大值
            multiplier *= b;
            sum += prev->current() * multiplier;
            prev = prev->_prev;  // 移动到下一个box
        }

        return sum;
    }


    /**
     * @brief 按位分解总和，将值分配给当前box 和左侧所有prev box
     *
     * @param sum
     * @param base
     */
    void split_sum(uint32_t sum, uint8_t base = 10) {
        SpinBox *current = this;
        while (current != nullptr /*&& current->is_range()*/) {
            auto b = (base > 0) ? base : current->_max;  // 如果没有基数，则使用最大值
            auto r = static_cast<uint8_t>(sum % b);      // 计算余数
            sum /= b;
            r = constrain(r, current->_min, current->_max - 1);  // 确保值在范围内
            current->set_current(r);                             // 设置当前box 的值
            current = current->_prev;                            // 移动到下一个box
        }

        set_changed(true);  // 设置为已改变状态
    }


    /**
     * @brief 在屏幕上绘制SpinBox
     *
     * @param is_initial 是否是初始绘制
     * @return true 绘制成功
     * @return false 没有变化，不需要重绘
     */
    bool draw(bool is_initial) {
        if (!is_initial && !changed()) {
            // 没有变化，不需要重绘
            return false;
        }

        set_changed(false);  // 重置

        u8g2.setFont(MENU_FONT);
        auto font_height = u8g2.getMaxCharHeight();
        auto top_y = _y - font_height;  // 顶部Y坐标
        clear_box(_x, top_y, _w, _h);
        u8g2.setCursor(_x, _y);

        if (_list == nullptr) {
            // 如果没有选项列表，直接显示当前值
            if (is_hex()) {
                // u8g2.print("0x");
                u8g2.print(_current, HEX);
            }
            else {
                u8g2.print(_current);
            }
        }
        else {
            // 如果有选项列表，显示当前选中的选项
            if (_current < _max) {
                u8g2.print(_list[_current]);
            }
            else {
                u8g2.print("N/A");  // 如果索引越界，显示"N/A"
            }
        }

        auto bottom_y = top_y + _h;

        if (focused()) {
            u8g2.drawBox(_x, bottom_y - 2, _w, 2);  // 绘制底部的光标线
        }

        return true;  // 返回true表示SpinBox已成功绘制
    }


    uint8_t x() const {
        return _x;
    }


    uint8_t y() const {
        return _y;
    }


    uint8_t width() const {
        return _w;
    }


    uint8_t height() const {
        return _h;
    }
};


class SpinBoxGroup {
   private:
    SpinBox *_boxes = nullptr;  // SpinBox 数组

    uint8_t _count = 0;          // SpinBox 数量
    uint8_t _focused_index = 0;  // 当前获得焦点的SpinBox索引

    bool _focused = false;      // 整个group 是否获得焦点
    bool _should_free = false;  // 是否需要释放SpinBox数组内存


    // 链接SpinBox，形成一个链表结构
    void link_boxes() {
        if (_count == 0)
            return;

        for (uint8_t i = 0; i < (_count - 1); ++i) {
            _boxes[i].link(&_boxes[i + 1]);  // 链接SpinBox
        }
        _boxes[_count - 1].link(nullptr);  // 最后一个SpinBox没有下一个
        _boxes[0].set_focused(true);       // 默认第一个SpinBox获得焦点
    }


   public:
    SpinBoxGroup() = default;


    ~SpinBoxGroup() {
        free_boxes();  // 析构时释放内存
    }


    void init_boxes(SpinBox *boxes, uint8_t count) {
        free_boxes();  // 释放之前的内存
        _boxes = boxes;
        _count = count;
        link_boxes();          // 链接SpinBox
        _should_free = false;  // 不需要释放内存
    }


    void allocate_boxes(uint8_t count) {
        free_boxes();  // 释放之前的内存
        _boxes = new SpinBox[count];
        _count = count;
        _should_free = true;
        link_boxes();
    }


    void free_boxes() {
        if (_should_free && _boxes != nullptr) {
            delete[] _boxes;  // 释放之前的内存
            _should_free = false;
        }
        _boxes = nullptr;  // 清空指针
        _count = 0;
    }


    void set_range(uint8_t min, uint8_t max, uint8_t current = 0) {
        if (_boxes != nullptr && _count > 0) {
            for (size_t i = 0; i < _count; ++i) {
                SpinBox &b = _boxes[i];
                b.set_range(min, max, current);  // 设置每个SpinBox的范围
            }
        }
    }


    void set_range(uint8_t index, uint8_t min, uint8_t max, uint8_t current = 0) {
        if (_boxes != nullptr && index < _count) {
            SpinBox &b = _boxes[index];
            b.set_range(min, max, current);  // 设置指定SpinBox的范围
        }
    }


    void set_list(ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) {
        if (_boxes != nullptr && _count > 0) {
            for (size_t i = 0; i < _count; ++i) {
                SpinBox &b = _boxes[i];
                b.set_list(list, count, current);  // 设置每个SpinBox的选项列表
            }
        }
    }


    void set_list(uint8_t index, ConstantStrArrayPtr list, uint8_t count, uint8_t current = 0) {
        if (_boxes != nullptr && index < _count) {
            SpinBox &b = _boxes[index];
            b.set_list(list, count, current);  // 设置指定SpinBox的选项列表
        }
    }


    const SpinBox *boxes() const {
        return _boxes;  // 返回SpinBox数组
    }


    uint8_t count() const {
        return _count;  // 返回SpinBox数量
    }


    void set_not_focused() {
        for (size_t i = 0; i < _count; ++i) {
            _boxes[i].set_focused(false);  // 设置所有SpinBox为未聚焦状态
        }
        _focused = false;  // 整个group 失去焦点
    }


    void set_focused(uint8_t index) {
        if (index < _count) {
            if (_boxes[index].focused()) {
                // 如果当前SpinBox已经聚焦，则不需要重复设置
                return;
            }

            _boxes[_focused_index].set_focused(false);  // 取消之前聚焦的SpinBox
            _boxes[index].set_focused(true);            // 设置指定SpinBox为聚焦状态
            _focused_index = index;                     // 更新当前聚焦的索引
            _focused = true;                            // 整个group 获得焦点
        }
    }


    void set_focused() {
        set_focused(_focused_index);  // 设置当前聚焦的SpinBox
    }


    void focus_left() {
        if (_count == 0)
            return;
        if (_focused_index < _count - 1) {
            set_focused(_focused_index + 1);  // 聚焦下一个SpinBox
        }
        else {
            set_focused(0);  // 如果已经是最后一个，则回到第一个
        }
    }


    void focus_right() {
        if (_count == 0)
            return;
        if (_focused_index > 0) {
            set_focused(_focused_index - 1);  // 聚焦上一个SpinBox
        }
        else {
            set_focused(_count - 1);  // 如果已经是第一个，则回到最后一个
        }
    }


    void inc() {
        if (_focused_index >= _count)
            return;

        SpinBox &current_box = _boxes[_focused_index];
        current_box.inc();  // 增加当前聚焦的SpinBox的值
    }


    void dec() {
        if (_focused_index >= _count)
            return;

        SpinBox &current_box = _boxes[_focused_index];
        current_box.dec();  // 减少当前聚焦的SpinBox的值
    }


    uint32_t sum(uint8_t base = 10) const {
        if (_count == 0)
            return 0;

        return _boxes[0].sum(base);  // 返回组内SpinBox的总和
    }


    void split_sum(uint32_t sum, uint8_t base = 10) {
        if (_count == 0)
            return;

        _boxes[0].split_sum(sum, base);  // 将总和分配给组内SpinBox
    }


    /**
     * @brief 设置每个box 的位置和尺寸
     *
     * @param x  最左侧spin box 的起始x 坐标
     * @param y  最左侧spin box 的起始y 坐标
     * @param w
     * @param h
     * @param spacing  间隔
     * @return uint8_t
     */
    uint8_t set_placing(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t spacing = 0) {
        if (_boxes != nullptr && _count > 0) {
            for (int i = _count - 1; i > -1; --i) {
                SpinBox &b = _boxes[i];
                b.set_placing(x, y, w, h);  // 设置每个SpinBox的位置和尺寸
                x += w + spacing;           // 更新下一个SpinBox的x坐标
            }
        }

        return x;
    }


    void set_hex(bool hex) {
        if (_boxes != nullptr && _count > 0) {
            for (size_t i = 0; i < _count; ++i) {
                SpinBox &b = _boxes[i];
                b.set_hex(hex);  // 设置每个SpinBox是否按16进制显示
            }
        }
    }


    bool draw(bool is_initial) {
        if (_boxes == nullptr || _count == 0) {
            return false;  // 如果没有SpinBox，返回false
        }

        bool changed = false;
        for (size_t i = 0; i < _count; ++i) {
            SpinBox &box = _boxes[i];
            if (box.draw(is_initial)) {
                changed = true;  // 如果有任何一个SpinBox绘制成功，则返回true
            }
        }

        return changed;  // 返回是否有任何SpinBox绘制成功
    }
};